package com.mvu.banana.internal.server;

import com.mvu.banana.domain.server.EMF;
import com.mvu.banana.common.server.AJAXServlet;
import com.mvu.banana.internal.generated.RunBoxDTO;

import javax.persistence.metamodel.EntityType;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;

public class RunBoxServlet extends AJAXServlet<RunBoxDTO> {
  private static final int MAX_SUGGESTED = 100; // TODO: can't handle more than this now
  private static final HashMap<String, String> roots = new HashMap<String, String>();

  static {
    int i = 0;
    String builderPackage = CredentialBuilder.class.getPackage().getName() + ".";
    Iterator<EntityType<?>> iterator = EMF.get().getMetamodel().getEntities().iterator();
    while (iterator.hasNext() && i < MAX_SUGGESTED) {
      EntityType<?> entity = iterator.next();
      roots.put(entity.getName(), entity.getJavaType().getName());
      roots.put(entity.getName() + "Builder", builderPackage + entity.getJavaType().getSimpleName() + "Builder");
      i++;
    }
  }

  public RunBoxDTO post(RunBoxDTO p0) {
    ArrayList<String> suggestions = new ArrayList<String>();
    try {
      if (p0.input.length() == 0) { // at start
        suggestions.addAll(roots.keySet());
      } else {
        String[] tokens = p0.input.split(" ");
        Class<?> clazz = findClass(tokens[0]);
        String className = clazz.getSimpleName();
        String prefix = className + " ";
        for (Method method : clazz.getMethods()) {
          String methodName = method.getName();
          suggestions.add(prefix + methodName);
          if (tokens.length > 1 && methodName.equals(tokens[1])) {
            // try to invoke
            Class<?>[] parameterTypes = method.getParameterTypes();
            int argsSoFar = tokens.length - 2;
            if (parameterTypes.length == argsSoFar) {
              p0.console = invoke(clazz, method, tokens);
            }
          }
        }
      }
    } catch (Exception e) {
      e.printStackTrace();
      StringBuilder error = new StringBuilder(e.toString()).append("\n");
      for (StackTraceElement element : e.getStackTrace()) {
        error.append(element.toString()).append("\n");
      }
      p0.console = error.toString();
    }
    p0.Suggestions = suggestions.toArray(new String[0]);
    return p0;
  }

  private String invoke(Class<?> clazz, Method method, String[] tokens) throws Exception {
    Class<?>[] parameterTypes = method.getParameterTypes();
    ArrayList parameters = new ArrayList();
    for (int i = 0; i < parameterTypes.length; i++) {
      Class type = parameterTypes[i];
      Object parameter = getParameter(type, tokens[2 + i]);
      parameters.add(parameter);
    }
    Object result;
    if (Modifier.isStatic(method.getModifiers())) {
      result = method.invoke(null, parameters.toArray());
    } else {
      Object obj = clazz.newInstance();
      result = method.invoke(obj, parameters.toArray());
      if(result instanceof BuilderBase){
        result = ((BuilderBase) result).commit();
      }else if(result instanceof Collection){
        StringBuilder builder = new StringBuilder();
        int i = 1;
        Iterator iterator = ((Collection) result).iterator();
        while(iterator.hasNext()){
          builder.append(i++).append(":").append(iterator.next().toString()).append("\n");
        }
        return builder.toString();
      }
    }
    return result == null ? null : result.toString();
  }

  private Object getParameter(Class type, String token) {
    if (Long.class.equals(type) || Long.TYPE.equals(type)) {
      return Long.valueOf(token);
    } else if (Integer.class.equals(type) || Integer.TYPE.equals(type)) {
      return Integer.valueOf(token);
    } else if (String.class.isAssignableFrom(type)) {
      return token;
    }
    throw new IllegalArgumentException("Cannot cast argument " + token + " to " + type);
  }


  private Class<?> findClass(String className) throws ClassNotFoundException {
    String fullName = roots.get(className);
    if(fullName != null){
      return Class.forName(fullName);
    }
    throw new IllegalArgumentException("Could not find builder for: " + className);
  }
}